package tman.system.peer.tman;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;

import se.sics.kompics.ComponentDefinition;
import se.sics.kompics.Handler;
import se.sics.kompics.Negative;
import se.sics.kompics.Positive;
import se.sics.kompics.network.Network;
import se.sics.kompics.timer.SchedulePeriodicTimeout;
import se.sics.kompics.timer.Timer;
import tman.simulator.snapshot.Snapshot;

import common.configuration.TManConfiguration;
import common.peer.PeerAddress;

import cyclon.system.peer.cyclon.CyclonPartnersPort;
import cyclon.system.peer.cyclon.CyclonPartnersRequest;
import cyclon.system.peer.cyclon.CyclonPartnersResponse;

public final class TMan extends ComponentDefinition {
	Negative<TManPartnersPort> tmanPartnersPort = negative(TManPartnersPort.class);
	Positive<CyclonPartnersPort> cyclonPartnersPort = positive(CyclonPartnersPort.class);
	Positive<Network> networkPort = positive(Network.class);
	Positive<Timer> timerPort = positive(Timer.class);

	private long period;
	private PeerAddress self;
	private PeerAddress succ;
	private PeerAddress pred;
	private ArrayList<PeerAddress> tmanPartners;
	private PeerAddress[] fingers = new PeerAddress[13]; // Assuming N=2^13
	private ArrayList<PeerAddress> cyclonPartners;
	private TManConfiguration tmanConfiguration;

	/*MY VARIABLES*/
	private static final Random RAND = new Random(System.currentTimeMillis());
	private static final int VIEW_MAX_SIZE = 6;

	//-------------------------------------------------------------------	
	public TMan() {
		tmanPartners = new ArrayList<PeerAddress>();
		cyclonPartners = new ArrayList<PeerAddress>();

		subscribe(handleInit, control);
		subscribe(handleRound, timerPort);
		subscribe(handleCyclonPartnersResponse, cyclonPartnersPort);
		subscribe(handleTManPartnersRequest, tmanPartnersPort);
		//WE DID
		subscribe(handleTManBufferRequest, networkPort);
		subscribe(handleTManBufferResponse, networkPort);
		//STOP
	}

	//-------------------------------------------------------------------	
	Handler<TManInit> handleInit = new Handler<TManInit>() {
		public void handle(TManInit init) {
			self = init.getSelf();
			tmanConfiguration = init.getConfiguration();
			period = tmanConfiguration.getPeriod();
			succ = self;
			pred = self;

			SchedulePeriodicTimeout rst = new SchedulePeriodicTimeout(period, period);
			rst.setTimeoutEvent(new TManSchedule(rst));
			trigger(rst, timerPort);

		}
	};

	//-------------------------------------------------------------------	
	Handler<TManSchedule> handleRound = new Handler<TManSchedule>() {
		public void handle(TManSchedule event) {
			CyclonPartnersRequest response = new CyclonPartnersRequest();
			trigger(response, cyclonPartnersPort);

		}
	};


	//-------------------------------------------------------------------	
	Handler<CyclonPartnersResponse> handleCyclonPartnersResponse = new Handler<CyclonPartnersResponse>() {
		public void handle(CyclonPartnersResponse event) {
			cyclonPartners = event.getPartners();
			Snapshot.updateCyclonPartners(self, cyclonPartners);

			//System.out.println("tman: " + self + " --> " + cyclonPartners);

			//What I did
			if(tmanPartners.size() > 0) {
				PeerAddress q = rank(self, tmanPartners).get(RAND.nextInt(tmanPartners.size())/2);
				List<PeerAddress> buffer = merge(self, tmanPartners);
				buffer = merge(buffer, cyclonPartners);
				TManBufferRequest request = new TManBufferRequest(self, q, buffer);
				trigger(request, networkPort);
			} else {
				tmanPartners = (ArrayList<PeerAddress>) selectView(self, cyclonPartners);
			}
			//End of what I did

		}
	};

	//-------------------------------------------------------------------	
	Handler<TManPartnersRequest> handleTManPartnersRequest = new Handler<TManPartnersRequest>() {
		public void handle(TManPartnersRequest event) {
			// Merging the tmanPartners for the ring, and the tmanPartners for the fingers
			ArrayList<PeerAddress> tmp = new ArrayList<PeerAddress>(tmanPartners);
			for(int i = 0; i < fingers.length; i++){
				tmp.add(fingers[i]);
			}
			TManPartnersResponse response = new TManPartnersResponse(tmp);
			trigger(response, tmanPartnersPort);
		}
	};

	/*
	 * WE DID
	 */
	Handler<TManBufferRequest> handleTManBufferRequest = new Handler<TManBufferRequest>() {

		@Override
		public void handle(TManBufferRequest event) {
			// Ring overlay
			List<PeerAddress> bufferP = event.getBuffer();
			List<PeerAddress> buffer = merge(self, tmanPartners);
			buffer = merge(buffer, cyclonPartners);
			TManBufferResponse response = new TManBufferResponse(self, event.getPeerSource(), buffer);
			trigger(response, networkPort);
			buffer = merge(bufferP, tmanPartners);
			tmanPartners = (ArrayList<PeerAddress>) selectView(self, buffer);
			Snapshot.updateTManPartners(self, tmanPartners);
			if (tmanPartners.size() > 0) {
				succ = tmanPartners.get(0);
			}
			if (tmanPartners.size() > 1) {
				pred = tmanPartners.get(1);
			}
			//System.out.println(self + " has as successor " + succ);
			Snapshot.updateSuccPred(self, succ, pred);

			// Small-world overlay
			for(int i = 1; i <= fingers.length; i++){
				if(fingers[i-1] == null){
					fingers[i-1] = (rankFinger(self, i, buffer)).get(0);
				} else {
					fingers[i-1] = (rankFinger(self, i, merge(fingers[i-1], buffer))).get(0);
				}
			}
			Snapshot.updateSmallWorldPartners(self, new ArrayList<PeerAddress>(Arrays.asList(fingers)));
		}

	};

	Handler<TManBufferResponse> handleTManBufferResponse = new Handler<TManBufferResponse>() {

		@Override
		public void handle(TManBufferResponse event) {
			// Ring overlay
			List<PeerAddress> buffer = merge(tmanPartners, event.getBuffer());
			tmanPartners = (ArrayList<PeerAddress>) selectView(self, buffer);
			Snapshot.updateTManPartners(self, tmanPartners);
			if (tmanPartners.size() > 0) {
				succ = tmanPartners.get(0);
			}
			if (tmanPartners.size() > 1) {
				pred = tmanPartners.get(1);
			}
			//System.out.println(self + " has as successor " + succ);
			Snapshot.updateSuccPred(self, succ, pred);

			// Small-world overlay
			for(int i = 1; i <= fingers.length; i++){
				if(fingers[i-1] == null){
					fingers[i-1] = (rankFinger(self, i, buffer)).get(0);
				} else {
					fingers[i-1] = (rankFinger(self, i, merge(fingers[i-1], buffer))).get(0);
				}
			}
			Snapshot.updateSmallWorldPartners(self, new ArrayList<PeerAddress>(Arrays.asList(fingers)));
		}


	};


	/*
	 * Methods used internaly.
	 * 
	 */

	private static List<PeerAddress> rank(PeerAddress descriptor, List<PeerAddress> view) {
		List<PeerAddress> tmp = quickSort(descriptor, view, 0, view.size()-1);
		//System.out.println(descriptor + ": " + tmp);
		List<PeerAddress> before = new ArrayList<PeerAddress>(tmp.subList(tmp.size()/2, tmp.size()));
		List<PeerAddress> after = new ArrayList<PeerAddress>(tmp.subList(0, tmp.size()/2));
		List<PeerAddress> results = new ArrayList<PeerAddress>();
		int i = 0;
		while(i < Math.min(before.size(), after.size())) {
			results.add(after.get(i));
			results.add(before.get(before.size()-i-1));
			i++;
		}
		if(tmp.size()%2 == 1) {
			results.add(tmp.get(tmp.size()/2));
		}
		//System.out.println(descriptor + ": " + results);
		return results;
	}


	private static List<PeerAddress> quickSort(PeerAddress descriptor, List<PeerAddress> tab, int first, int last) {
		if(first < last) {
			int pivotIndex = partition(descriptor, tab, first, last, first);
			tab = quickSort(descriptor, tab, first, pivotIndex-1);
			tab = quickSort(descriptor, tab, pivotIndex+1, last);
		}
		return tab;
	}

	private static int partition(PeerAddress descriptor, List<PeerAddress> tab, int first, int last, int pivotIndex) {
		PeerAddress pivot = tab.get(pivotIndex);
		tab.set(pivotIndex, tab.get(last));
		tab.set(last, pivot);
		int j = first;
		for(int i = first; i < last; i++) {
			if(descriptor.closerThanOnARing(tab.get(i), tab.get(last))) {
				PeerAddress tmp = tab.get(i);
				tab.set(i, tab.get(j));
				tab.set(j, tmp);
				j++;
			}
		}
		PeerAddress tmp = tab.get(last);
		tab.set(last, tab.get(j));
		tab.set(j, tmp);
		return j;
	}

	private static List<PeerAddress> merge(PeerAddress a, List<PeerAddress> list) {
		List<PeerAddress> r = new ArrayList<PeerAddress>(list);
		if(r.indexOf(a) == -1) {
			r.add(a);
		}
		return r;
	}

	private static List<PeerAddress> merge(List<PeerAddress> a, List<PeerAddress> b) {
		List<PeerAddress> r = new ArrayList<PeerAddress>(a);
		for(PeerAddress p : b) {
			if(r.indexOf(p) == -1) {
				r.add(p);
			}
		}
		return r;
	}

	private static List<PeerAddress> selectView(PeerAddress descriptor, List<PeerAddress> list) {
		List<PeerAddress> r = new ArrayList<PeerAddress>(list);
		int i = r.indexOf(descriptor);
		if(i != -1) {
			r.remove(i);
		}
		return new ArrayList<PeerAddress>(rank(descriptor, r).subList(0, Math.min(VIEW_MAX_SIZE, r.size())));
	}
	//STOP

	// Sort the view to get the best ith finger (ie the closest node to descriptor+2^i)
	private static List<PeerAddress> rankFinger(final PeerAddress peer, final int i, List<PeerAddress> view){
		Collections.sort(view, new Comparator<PeerAddress>() {
			/*
			 * Normal, for task 2.
			 *
			public int compare(PeerAddress p1, PeerAddress p2) {
				int distance1 = PeerAddress.distanceOnARing(p1.getPeerId().intValue(), peer.getPeerId().intValue()+(int)Math.pow(2, i));
				int distance2 = PeerAddress.distanceOnARing(p2.getPeerId().intValue(), peer.getPeerId().intValue()+(int)Math.pow(2, i));
				return distance1 - distance2;
			}
			/*
			 * Optimized for task 4.
			 */
			public int compare(PeerAddress p1, PeerAddress p2) {
				return peer.getRankFinger(i, p1) - peer.getRankFinger(i, p2);
			}
		});
		return view;
	}
	
}
